import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import net.miginfocom.swing.MigLayout;

/**
 * A graphical user interface for a chat application.
 * 
 * @author Peter Sundqvist & Modified by. Emanuel Y. Lindgren.
 */
public class ChatGUI extends JFrame {
    /**
    *
    */
    private static final long serialVersionUID = 1L;

    private final ChatInterface m_interface;
    // lastInput used for input history.
    private String lastInput = "";
    // holds the last searchresults.
    private final ArrayList<String> searchResults = new ArrayList<String>();
    // needed for interacting with the client outside the GUI.
    ChatClient cClient;

    // Tabs
    private final JTabbedPane m_tabs;
    private final JPanel m_nameServerPanel;
    private final JPanel m_chatPanel;

    // Name server panel content
    private final JToolBar m_nsToolBar;
    private final JTextField m_nsHost;
    private final JTextField m_nsPort;
    private final JLabel m_nsHostText;
    private final JLabel m_nsPortText;
    private final JButton m_nsConnectBtn;
    private final JScrollPane m_nsScroll;
    private final JList m_serverList;
    private final JPanel m_serverConnectPanel;
    private JTextField m_nickname;
    private JLabel m_nicknameText;

    // Chat panel content
    private final JToolBar m_chatToolBar;
    private final JButton m_disconnectBtn;
    private final JLabel m_topic;
    private final JEditorPane m_output; // Where messages are pritned
    private final JScrollPane m_scrollOutput;
    private final JList m_clientList;
    private final JScrollPane m_scrollClientList;
    private final JPanel m_sendPanel;
    private final JTextField m_input; // Input for message to send
    private final JButton m_sendBtn;
    private final StringBuffer m_text = new StringBuffer(); // Chat server text
    private final JPanel tab_downloads;
    private final JPanel tab_uploads;
    private final JTextField tfFileSearch;
    private final JButton btnSearchAll;
    private final JToolBar tb_downloads;
    private final JSplitPane splitPane;
    private final JButton btnDownloadSelected;
    private JButton btnRefreshFileList;
    private JScrollPane scrollPane;
    private JList userList;
    private JScrollPane scrollPane_1;
    private JList FilesList;
    private JScrollPane dloadProgress;
    private JPanel dlProgressPanel;
    private JSplitPane splitPane_1;
    private JPanel ulProgressPanel;
    private JButton btnCancel;
    private JPanel placeholder;

    /**
     * Create a new chat.
     * 
     * @param name
     *            - the name in the title bar for the chat window.
     * @param ci
     *            - the chat interface which will be called on GUI actions.
     * @param useNameServer
     *            - true if the chat client uses a name server to find chat
     *            servers
     * @param useEncrypt
     *            - true if the chat client uses encryption/compression
     */
    public static void create(final String name, final ChatInterface ci) {
	SwingUtilities.invokeLater(new Runnable() {
	    @Override
	    public void run() {
		new ChatGUI(name, ci);
	    }
	});
    }

    /**
     * Enable or disable the chat tab.
     * 
     * @param value
     *            - true if enabled, false otherwise
     */
    public void setChatEnabled(final boolean value) {
	SwingUtilities.invokeLater(new Runnable() {
	    @Override
	    public void run() {
		m_tabs.setEnabledAt(1, value);
	    }
	});
    }

    /**
     * Add a new upload progress bar to the upload tab panel.
     * 
     * @param string
     *            to display on the progressbar.
     * 
     */
    public JProgressBar addNewUploadProgressBar(String displaystr) {
	JProgressBar progressBar = new JProgressBar();
	progressBar.setStringPainted(true);
	progressBar.setForeground(Color.red);
	progressBar.setString(displaystr);
	ulProgressPanel.add(progressBar);
	ulProgressPanel.validate();
	ulProgressPanel.repaint();
	return progressBar;
    }

    /**
     * Remove upload progress bar from the upload tab panel.
     * 
     * @param JProgressBar
     * 
     */
    public void removeUploadProgressBar(final JProgressBar pBar) {
	SwingUtilities.invokeLater(new Runnable() {
	    @Override
	    public void run() {
		ulProgressPanel.remove(pBar);
		ulProgressPanel.validate();
		ulProgressPanel.repaint();
	    }
	});
    }

    /**
     * Add a new download progress bar to the download tab panel.
     * 
     * @param string
     *            to display on the progressbar.
     * 
     */
    public JProgressBar addNewDownloadProgressBar(String displaystr) {
	JProgressBar progressBar = new JProgressBar();
	progressBar.setStringPainted(true);
	progressBar.setString(displaystr);
	dlProgressPanel.add(progressBar);
	dlProgressPanel.validate();
	dlProgressPanel.repaint();
	return progressBar;
    }

    /**
     * Remove upload progress bar from the upload tab panel.
     * 
     * @param JProgressBar
     * 
     */
    public void removeDownloadProgressBar(final JProgressBar pBar) {
	SwingUtilities.invokeLater(new Runnable() {
	    @Override
	    public void run() {
		dlProgressPanel.remove(pBar);
		dlProgressPanel.validate();
		dlProgressPanel.repaint();
	    }
	});
    }

    /**
     * Enable or disable the filesharing tabs.
     * 
     * @param value
     *            - true if enabled, false otherwise
     */
    public void setFilesharingEnabled(final boolean value) {
	SwingUtilities.invokeLater(new Runnable() {
	    @Override
	    public void run() {
		m_tabs.setEnabledAt(2, value);
		m_tabs.setEnabledAt(3, value);
	    }
	});
    }

    /**
     * Set the title of the chat tab. Don't alter the supplied string after a
     * call to this method.
     * 
     * @param title
     *            - the text to show on the chat tab
     */
    public void setChatTitle(final String title) {
	SwingUtilities.invokeLater(new Runnable() {
	    @Override
	    public void run() {
		m_tabs.setTitleAt(1, title);
	    }
	});
    }

    /**
     * Set the topic field inside the chat tab.
     * 
     * @param topic
     *            - the text to show in the topic label.
     */
    public void setChatTopic(final String topic) {
	SwingUtilities.invokeLater(new Runnable() {
	    @Override
	    public void run() {
		m_topic.setText(" [TOPIC]> " + topic);
	    }
	});
    }

    /**
     * Set which tab to show.
     * 
     * @param index
     *            - 0 is the name server tab 1 is the chat tab
     */
    public void setSelectedTab(final int index) {
	SwingUtilities.invokeLater(new Runnable() {
	    @Override
	    public void run() {
		m_tabs.setSelectedIndex(index);
	    }
	});
    }

    /**
     * Emulate a click on the chat gui disconnect button.
     * 
     */
    public void clickM_disconnectBtn() {
	m_disconnectBtn.doClick();
    }

    /**
     * Add another response to the Xcher search and update the result List in
     * gui. The old gui list cant be modified to a new list has to be set. Keep
     * in mind this is an expensive method to call repeatedly.
     * 
     * @param string
     *            to be displayed in the results list.
     */
    public void addXcherSearchResult(final String str) {
	SwingUtilities.invokeLater(new Runnable() {
	    @Override
	    public void run() {
		searchResults.add(str);
		FilesList.setListData(searchResults.toArray());
	    }
	});
    }

    /**
     * Set which nicknames to show in the XCHER User list. The list should not
     * be altered after a call to this method.
     * 
     * @param objects
     *            - the nicknames of the clients connected to the current
     *            server.
     */
    public void setXcherUserList(final Object[] objects) {
	SwingUtilities.invokeLater(new Runnable() {
	    @Override
	    public void run() {
		userList.setListData(objects);
	    }
	});
    }

    /**
     * Set which nicknames to show in the client list. The list should not be
     * altered after a call to this method.
     * 
     * @param objects
     *            - the nicknames of the clients connected to the current
     *            server.
     */
    public void setClientList(final Object[] objects) {
	SwingUtilities.invokeLater(new Runnable() {
	    @Override
	    public void run() {
		m_clientList.setListData(objects);
	    }
	});
    }

    /**
     * Print text to the chat output box.
     * 
     * @param text
     *            - a string to be printed.
     */
    public void print(final String text) {
	SwingUtilities.invokeLater(new Runnable() {
	    @Override
	    public void run() {
		// if text exceeds 2200chars the oldest 200 are deleted.
		if (m_text.length() > 2200) {
		    m_text.delete(0, 400);
		    m_text.append(text);
		} else {
		    m_text.append(text);
		}

		// Here you can chage the overall look of the chat output.
		// Also if you don't want HTML remove all markup code here.
		m_output.setText("<html><head><title>Chat</title></head><body bgcolor=\"silver\"><font face=arial size=4 color=\"green\">"
			+ m_text + "</font></body></html>");
	    }
	});
    }

    /**
     * Print text to the chat output pane, also end the text with a line break.
     * 
     * @param text
     *            - a string to be printed.
     */
    public void println(final String text) {
	SwingUtilities.invokeLater(new Runnable() {
	    @Override
	    public void run() {
		print(text + "<br>");
	    }
	});
    }

    /**
     * Clear the chat output pane.
     */
    public void clear() {
	SwingUtilities.invokeLater(new Runnable() {
	    @Override
	    public void run() {
		m_text.setLength(0);
		print("");
	    }
	});
    }

    /* Constructor. Edit this to change look and functionality. */
    private ChatGUI(String name, ChatInterface ci) {
	super(name);
	final ChatGUI gui = this;

	m_interface = ci;
	m_interface.setGUI(this);
	cClient = m_interface.getClient();

	// Create tabs
	m_tabs = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.WRAP_TAB_LAYOUT);
	m_nameServerPanel = new JPanel();
	m_chatPanel = new JPanel();

	// Create name server panel content
	m_nsToolBar = new JToolBar();
	m_nsHost = new JTextField(25);
	m_nsPort = new JTextField(7);
	m_nsHostText = new JLabel("Host");
	m_nsPortText = new JLabel("Port");
	m_nsConnectBtn = new JButton("Connect");
	m_nickname = new JTextField(10);
	m_nicknameText = new JLabel("Nickname");
	m_nsScroll = new JScrollPane(
		ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
		ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
	m_serverList = new JList();
	m_serverConnectPanel = new JPanel();
	m_nickname = new JTextField(10);
	m_nicknameText = new JLabel("Nickname");

	// Create chat panel content
	m_chatToolBar = new JToolBar();

	m_disconnectBtn = new JButton("Disconnect");
	m_topic = new JLabel("");
	m_output = new JEditorPane("text/html", ""); // Change here if you don't
						     // want HTML
	m_scrollOutput = new JScrollPane(
		ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
		ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
	m_clientList = new JList();
	m_scrollClientList = new JScrollPane(
		ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
		ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
	m_sendPanel = new JPanel();
	m_input = new JTextField(
		"Type /name for commands, else interpreted as chat msg. Example: /NICK \"He man\" or /JOIN ");
	m_sendBtn = new JButton("Send");

	// Connect tabs
	getContentPane().setLayout(new BorderLayout());
	this.getContentPane().add(m_tabs, BorderLayout.CENTER);

	m_tabs.addTab("Chat Server", m_nameServerPanel);
	m_tabs.addTab("Chat", m_chatPanel);

	// Connect name server panel components
	m_nameServerPanel.setLayout(new BorderLayout());
	m_nameServerPanel.add(m_nsToolBar, BorderLayout.NORTH);

	m_nsScroll.getViewport().setView(m_serverList);

	m_nsToolBar.setLayout(new FlowLayout(FlowLayout.LEFT));
	m_nsToolBar.add(m_nsHostText);
	m_nsToolBar.add(m_nsHost);
	m_nsToolBar.add(m_nsPortText);
	m_nsToolBar.add(m_nsPort);
	m_nsToolBar.add(m_nicknameText);
	m_nsToolBar.add(m_nickname);
	m_nsToolBar.add(m_nsConnectBtn);

	m_serverConnectPanel.setLayout(new FlowLayout(FlowLayout.LEFT));

	// Connect chat panel components
	m_chatPanel.setLayout(new BorderLayout());
	m_chatPanel.add(m_sendPanel, BorderLayout.SOUTH);
	m_chatPanel.add(m_chatToolBar, BorderLayout.NORTH);
	m_chatPanel.add(m_scrollOutput, BorderLayout.CENTER);
	m_chatPanel.add(m_scrollClientList, BorderLayout.EAST);

	m_chatToolBar.setLayout(new FlowLayout(FlowLayout.LEFT));
	m_chatToolBar.add(m_disconnectBtn);
	m_chatToolBar.add(m_topic);

	m_scrollOutput.getViewport().setView(m_output);
	m_scrollClientList.getViewport().setView(m_clientList);

	m_sendPanel.setLayout(new BorderLayout());
	m_sendPanel.add(m_input, BorderLayout.CENTER);
	m_sendPanel.add(m_sendBtn, BorderLayout.EAST);

	// Set properties
	m_nsToolBar.setFloatable(false);
	m_serverList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
	m_nickname.setText("Dr. Zoidberg");

	m_tabs.setEnabledAt(1, false);
	m_chatToolBar.setFloatable(false);

	// create filesharing tabs
	tab_downloads = new JPanel();
	m_tabs.addTab("Downloads", tab_downloads);
	tab_downloads.setLayout(new MigLayout("", "[789px,grow][]",
		"[][][195px,grow][][][grow][grow][][][]"));
	m_tabs.setEnabledAt(2, false);

	tb_downloads = new JToolBar();
	tab_downloads.add(tb_downloads, "cell 0 0");

	tfFileSearch = new JTextField();
	tb_downloads.add(tfFileSearch);
	tfFileSearch.setColumns(50);

	btnSearchAll = new JButton("Search All");

	tb_downloads.add(btnSearchAll);
	btnSearchAll.setHorizontalTextPosition(SwingConstants.CENTER);
	btnSearchAll.setActionCommand("");

	splitPane = new JSplitPane();
	splitPane.setResizeWeight(0.1);
	tab_downloads.add(splitPane, "cell 0 2 1 2,grow");

	scrollPane = new JScrollPane();
	splitPane.setLeftComponent(scrollPane);

	userList = new JList();

	scrollPane.setViewportView(userList);

	dloadProgress = new JScrollPane();
	splitPane.setRightComponent(dloadProgress);

	dlProgressPanel = new JPanel();
	dloadProgress.setViewportView(dlProgressPanel);
	dlProgressPanel.setLayout(new GridLayout(0, 1, 0, 0));

	scrollPane_1 = new JScrollPane();
	tab_downloads.add(scrollPane_1, "cell 0 5 1 4,grow");

	FilesList = new JList();
	scrollPane_1.setViewportView(FilesList);

	btnDownloadSelected = new JButton("Download Selected");

	tab_downloads.add(btnDownloadSelected,
		"flowx,cell 0 9,alignx left,aligny center");

	btnRefreshFileList = new JButton("Get Shared Files");
	btnRefreshFileList.setEnabled(false);

	tab_downloads.add(btnRefreshFileList, "cell 0 1");

	btnCancel = new JButton("Kill Connection");
	tab_downloads.add(btnCancel, "cell 0 9");

	tab_uploads = new JPanel();
	m_tabs.addTab("Uploads", tab_uploads);
	tab_uploads.setLayout(new BoxLayout(tab_uploads, BoxLayout.X_AXIS));

	splitPane_1 = new JSplitPane();
	splitPane_1.setResizeWeight(0.7);
	splitPane_1.setOrientation(JSplitPane.VERTICAL_SPLIT);
	tab_uploads.add(splitPane_1);

	ulProgressPanel = new JPanel();
	splitPane_1.setLeftComponent(ulProgressPanel);
	ulProgressPanel.setLayout(new GridLayout(0, 1, 0, 0));

	placeholder = new JPanel();
	splitPane_1.setRightComponent(placeholder);
	m_tabs.setEnabledAt(3, false);

	m_output.setEditable(false);
	m_clientList.setFixedCellWidth(150);
	m_clientList.setFocusable(false);

	this.setLocation(30, 30);
	this.setFocusable(true);
	this.setSize(800, 600);
	this.setMinimumSize(new Dimension(800, 600));
	this.setVisible(true);

	// Add listeners

	// modified to close any connection prior to exit.
	gui.addWindowListener(new WindowAdapter() {
	    @Override
	    public void windowClosing(WindowEvent winEvt) {
		m_interface.disconnectChatServer();
		System.exit(0);
	    }
	});

	btnCancel.addActionListener(new ActionListener() {
	    @Override
	    public void actionPerformed(ActionEvent e) {
		// user must be selected for this to work.
		if (userList.isSelectionEmpty()) {
		    return;
		}
		String nick = userList.getSelectedValue().toString();
		// check that a connection has been established to begin with.
		if (cClient.getXcherSocket().get(nick) != null) {
		    // kill all Xcher connections to target nick.
		    cClient.sendXcherCom(nick, "X_END");
		}

	    }
	});

	btnRefreshFileList.addActionListener(new ActionListener() {
	    @Override
	    public void actionPerformed(ActionEvent e) {

		String nick = userList.getSelectedValue().toString();
		cClient.sendXcherCom(nick, "X_FILELIST");
		XcherServer nickTjob = cClient.getXcherSocket().get(nick);

		while (!nickTjob.hasFileList()) {
		    // wait for the filelist to be populated.
		    try {
			// if target is marked as not supporting XCHER we exit
			// here.
			if (!nickTjob.isSupport()) {
			    break;
			}
			Thread.sleep(100);
		    } catch (InterruptedException e1) {
			e1.printStackTrace();
		    }
		}
		FilesList.setListData(nickTjob.getRequestedFileList());
		btnRefreshFileList.setEnabled(false);
	    }
	});

	userList.addListSelectionListener(new ListSelectionListener() {
	    @Override
	    public void valueChanged(ListSelectionEvent arg0) {
		/*
		 * disables or enables "get filelist" button based on if the
		 * filelist has already been retrieved.
		 */
		try {
		    String nick = userList.getSelectedValue().toString();
		    XcherServer nicktjob = cClient.getXcherSocket().get(nick);

		    if (nicktjob == null
			    || (!nicktjob.hasFileList() && nicktjob.isSupport())) {
			btnRefreshFileList.setEnabled(true);
			return;
		    } else if (nicktjob.hasFileList()) {
			FilesList.setListData(nicktjob.getRequestedFileList());
			btnRefreshFileList.setEnabled(false);
		    }
		} catch (Exception e1) {

		}
	    }
	});

	btnDownloadSelected.addActionListener(new ActionListener() {
	    @Override
	    public void actionPerformed(ActionEvent e) {
		if (FilesList.isSelectionEmpty()) {
		    return;
		}
		String RequestString = FilesList.getSelectedValue().toString();
		// if its a search result string exit.
		if (RequestString.endsWith("YES")) {
		    return;
		}
		String nick = userList.getSelectedValue().toString();
		cClient.sendXcherCom(nick, "X_REQUEST " + RequestString);
	    }
	});

	btnSearchAll.addActionListener(new ActionListener() {
	    @Override
	    public void actionPerformed(ActionEvent arg0) {
		String searchStr = tfFileSearch.getText().trim();
		if (searchStr.isEmpty()) {
		    return;
		}
		// clear any previous search results.
		searchResults.clear();

		if (searchStr.contains(" ")) {
		    searchStr = "\"" + searchStr + "\"";
		}
		int s = userList.getModel().getSize();

		for (int i = 0; i < s; i++) {
		    String nick = userList.getModel().getElementAt(i)
			    .toString();
		    cClient.sendXcherCom(nick, "X_CHK " + searchStr);
		}

	    }
	});

	m_nsPort.addKeyListener(new KeyAdapter() {
	    @Override
	    public void keyReleased(KeyEvent event) {
		if (event.getKeyCode() == KeyEvent.VK_ENTER) {
		    m_nsConnectBtn.doClick();
		}
	    }
	});

	m_nickname.addKeyListener(new KeyAdapter() {
	    @Override
	    public void keyReleased(KeyEvent event) {
		if (event.getKeyCode() == KeyEvent.VK_ENTER) {
		    m_nsConnectBtn.doClick();
		}
	    }
	});
	m_nsHost.addKeyListener(new KeyAdapter() {
	    @Override
	    public void keyReleased(KeyEvent event) {
		if (event.getKeyCode() == KeyEvent.VK_ENTER) {
		    m_nsConnectBtn.doClick();
		}
	    }
	});

	m_nsConnectBtn.addActionListener(new ActionListener() {
	    @Override
	    public void actionPerformed(ActionEvent event) {
		String hostText = m_nsHost.getText();
		String portText = m_nsPort.getText();
		String nickText = m_nickname.getText();
		// refuse if port is not a valid integer. display error dialog.
		try {
		    Integer.parseInt(portText);
		} catch (NumberFormatException e) {
		    JOptionPane.showMessageDialog(gui,
			    "Port must be an integer.", "Error",
			    JOptionPane.ERROR_MESSAGE);
		    return;
		}
		// refuse if host or port is empty. display error dialog.
		if (hostText.isEmpty() || portText.isEmpty()) {
		    JOptionPane.showMessageDialog(gui,
			    "Missing connection parameters.\n"
				    + "Host address and Port is required.",
			    "Error", JOptionPane.ERROR_MESSAGE);
		    return;
		}
		m_tabs.setEnabledAt(0, false);
		m_interface.connectChatServer(hostText, portText, nickText);
	    }
	});

	m_disconnectBtn.addActionListener(new ActionListener() {
	    @Override
	    public void actionPerformed(ActionEvent event) {
		m_tabs.setEnabledAt(0, true);
		m_interface.disconnectChatServer();

	    }
	});

	m_sendBtn.addActionListener(new ActionListener() {
	    @Override
	    public void actionPerformed(ActionEvent event) {
		lastInput = m_input.getText();
		m_interface.send(lastInput);
		m_input.setText("");
	    }
	});

	m_input.addKeyListener(new KeyAdapter() {
	    @Override
	    public void keyReleased(KeyEvent event) {
		if (event.getKeyCode() == KeyEvent.VK_ENTER) {
		    m_sendBtn.doClick();
		} else if (event.getKeyCode() == KeyEvent.VK_UP) {
		    // if arrow-up is pressed we set it to previous input.
		    m_input.setText(lastInput);
		}
	    }
	});
    }
}
